import time
from datetime import datetime, timedelta
from decimal import Decimal
from unittest import mock

import pytest
from django_countries.fields import Country
from django_scopes import scopes_disabled
from pytz import UTC

from pretix.base.models import (
    CartPosition,
    InvoiceAddress,
    Item,
    ItemAddOn,
    ItemBundle,
    ItemCategory,
    ItemVariation,
    Order,
    OrderPosition,
    Question,
    QuestionOption,
    Quota,
)
from pretix.base.models.orders import OrderFee


@pytest.fixture
def category(event):
    return event.categories.create(name='Tickets')


@pytest.fixture
def category2(event2):
    return event2.categories.create(name='Tickets2')


@pytest.fixture
def category3(event, item):
    cat = event.categories.create(name='Tickets')
    item.category = cat
    item.save()
    return cat


@pytest.fixture
def order(event, item, taxrule):
    testtime = datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC)

    with mock.patch('django.utils.timezone.now') as mock_now:
        mock_now.return_value = testtime
        o = Order.objects.create(
            code='FOO',
            event=event,
            email='dummy@dummy.test',
            status=Order.STATUS_PENDING,
            secret='k24fiuwvu8kxz3y1',
            datetime=datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC),
            expires=datetime(2017, 12, 10, 10, 0, 0, tzinfo=UTC),
            total=23,
            locale='en',
        )
        o.fees.create(
            fee_type=OrderFee.FEE_TYPE_PAYMENT,
            value=Decimal('0.25'),
            tax_rate=Decimal('19.00'),
            tax_value=Decimal('0.05'),
            tax_rule=taxrule,
        )
        InvoiceAddress.objects.create(order=o, company='Sample company', country=Country('NZ'))
        return o


@pytest.fixture
def order_position(item, order, taxrule, variations):
    op = OrderPosition.objects.create(
        order=order,
        item=item,
        variation=variations[0],
        tax_rule=taxrule,
        tax_rate=taxrule.rate,
        tax_value=Decimal('3'),
        price=Decimal('23'),
        attendee_name_parts={'full_name': 'Peter'},
        secret='z3fsn8jyufm5kpk768q69gkbyr5f4h6w',
    )
    return op


@pytest.fixture
def cart_position(event, item, variations):
    testtime = datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC)

    with mock.patch('django.utils.timezone.now') as mock_now:
        mock_now.return_value = testtime
        c = CartPosition.objects.create(
            event=event,
            item=item,
            datetime=testtime,
            expires=testtime + timedelta(days=1),
            variation=variations[0],
            price=Decimal('23'),
            cart_id='z3fsn8jyufm5kpk768q69gkbyr5f4h6w',
        )
        return c


TEST_CATEGORY_RES = {
    'name': {'en': 'Tickets'},
    'description': {'en': ''},
    'internal_name': None,
    'position': 0,
    'is_addon': False,
}


@pytest.mark.django_db
def test_category_list(token_client, organizer, event, team, category):
    res = dict(TEST_CATEGORY_RES)
    res['id'] = category.pk
    resp = token_client.get('/api/v1/organizers/{}/events/{}/categories/'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [res] == resp.data['results']
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/categories/?is_addon=false'.format(organizer.slug, event.slug)
    )
    assert resp.status_code == 200
    assert [res] == resp.data['results']
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/categories/?is_addon=true'.format(organizer.slug, event.slug)
    )
    assert resp.status_code == 200
    assert [] == resp.data['results']
    category.is_addon = True
    category.save()
    res['is_addon'] = True
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/categories/?is_addon=true'.format(organizer.slug, event.slug)
    )
    assert resp.status_code == 200
    assert [res] == resp.data['results']

    category.log_action('foo')
    resp = token_client.get('/api/v1/organizers/{}/events/{}/categories/'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    lmd = resp['Last-Modified']
    assert lmd
    time.sleep(1)
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/categories/'.format(organizer.slug, event.slug),
        HTTP_IF_MODIFIED_SINCE=lmd,
    )
    assert resp.status_code == 304
    time.sleep(1)
    category.log_action('foo')
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/categories/'.format(organizer.slug, event.slug),
        HTTP_IF_MODIFIED_SINCE=lmd,
    )
    assert resp.status_code == 200


@pytest.mark.django_db
def test_category_detail(token_client, organizer, event, team, category):
    res = dict(TEST_CATEGORY_RES)
    res['id'] = category.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/categories/{}/'.format(organizer.slug, event.slug, category.pk)
    )
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_category_create(token_client, organizer, event, team):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/categories/'.format(organizer.slug, event.slug),
        {
            'name': {'en': 'Tickets'},
            'description': {'en': ''},
            'position': 0,
            'is_addon': False,
        },
        format='json',
    )
    assert resp.status_code == 201


@pytest.mark.django_db
def test_category_update(token_client, organizer, event, team, category):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/categories/{}/'.format(organizer.slug, event.slug, category.pk),
        {
            'name': {'en': 'Test'},
        },
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        assert ItemCategory.objects.get(pk=category.pk).name == {'en': 'Test'}


@pytest.mark.django_db
def test_category_update_wrong_event(token_client, organizer, event2, category):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/categories/{}/'.format(organizer.slug, event2.slug, category.pk),
        {
            'name': {'en': 'Test'},
        },
        format='json',
    )
    assert resp.status_code == 404


@pytest.mark.django_db
def test_category_delete(token_client, organizer, event, category3, item):
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/categories/{}/'.format(organizer.slug, event.slug, category3.pk)
    )
    assert resp.status_code == 204
    with scopes_disabled():
        assert not event.categories.filter(pk=category3.id).exists()
        assert Item.objects.get(pk=item.pk).category is None


@pytest.fixture
def item(event):
    item = event.items.create(name='Budget Ticket', default_price=23)
    item.meta_values.create(property=event.item_meta_properties.first(), value='Tuesday')
    return item


@pytest.fixture
def item2(event2):
    return event2.items.create(name='Budget Ticket', default_price=23)


@pytest.fixture
def item3(event):
    return event.items.create(name='Budget Ticket', default_price=23)


TEST_ITEM_RES = {
    'name': {'en': 'Budget Ticket'},
    'internal_name': None,
    'default_price': '23.00',
    'sales_channels': ['web'],
    'category': None,
    'active': True,
    'description': None,
    'free_price': False,
    'tax_rate': '0.00',
    'tax_rule': None,
    'admission': False,
    'issue_giftcard': False,
    'position': 0,
    'generate_tickets': None,
    'allow_waitinglist': True,
    'picture': None,
    'available_from': None,
    'available_until': None,
    'require_bundling': False,
    'require_voucher': False,
    'hide_without_voucher': False,
    'allow_cancel': True,
    'min_per_order': None,
    'max_per_order': None,
    'hidden_if_available': None,
    'checkin_attention': False,
    'has_variations': False,
    'require_approval': False,
    'variations': [],
    'addons': [],
    'bundles': [],
    'show_quota_left': None,
    'original_price': None,
    'meta_data': {'day': 'Tuesday'},
}


@pytest.mark.django_db
def test_item_list(token_client, organizer, event, team, item):
    cat = event.categories.create(name='foo')
    res = dict(TEST_ITEM_RES)
    res['id'] = item.pk
    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [res] == resp.data['results']

    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?active=true'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [res] == resp.data['results']

    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?active=false'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [] == resp.data['results']

    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/items/?category={}'.format(organizer.slug, event.slug, cat.pk)
    )
    assert resp.status_code == 200
    assert [] == resp.data['results']

    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?admission=true'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [] == resp.data['results']

    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?admission=false'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [res] == resp.data['results']

    item.admission = True
    item.save()
    res['admission'] = True

    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?admission=true'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [res] == resp.data['results']

    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?admission=false'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [] == resp.data['results']

    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?tax_rate=0'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [res] == resp.data['results']

    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?tax_rate=19'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [] == resp.data['results']

    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/?free_price=true'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [] == resp.data['results']


@pytest.mark.django_db
def test_item_detail(token_client, organizer, event, team, item):
    res = dict(TEST_ITEM_RES)
    res['id'] = item.pk
    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_item_detail_variations(token_client, organizer, event, team, item):
    with scopes_disabled():
        var = item.variations.create(value='Children')
    res = dict(TEST_ITEM_RES)
    res['id'] = item.pk
    res['variations'] = [
        {
            'id': var.pk,
            'value': {'en': 'Children'},
            'default_price': None,
            'price': '23.00',
            'active': True,
            'description': None,
            'position': 0,
            'original_price': None,
        }
    ]
    res['has_variations'] = True
    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
    assert resp.status_code == 200
    assert res['variations'] == resp.data['variations']


@pytest.mark.django_db
def test_item_detail_addons(token_client, organizer, event, team, item, category):
    item.addons.create(addon_category=category)
    res = dict(TEST_ITEM_RES)

    res['id'] = item.pk
    res['addons'] = [
        {
            'addon_category': category.pk,
            'min_count': 0,
            'max_count': 1,
            'position': 0,
            'multi_allowed': False,
            'price_included': False,
        }
    ]
    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_item_detail_bundles(token_client, organizer, event, team, item, category):
    with scopes_disabled():
        i = event.items.create(name='Included thing', default_price=2)
        item.bundles.create(bundled_item=i, count=1, designated_price=2)
    res = dict(TEST_ITEM_RES)

    res['id'] = item.pk
    res['bundles'] = [
        {
            'bundled_item': i.pk,
            'bundled_variation': None,
            'count': 1,
            'designated_price': '2.00',
        }
    ]
    resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_item_create(token_client, organizer, event, item, category, taxrule):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'sales_channels': ['web', 'pretixpos'],
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': True,
            'issue_giftcard': False,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'meta_data': {'day': 'Wednesday'},
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        assert Item.objects.get(pk=resp.data['id']).sales_channels == [
            'web',
            'pretixpos',
        ]
        assert Item.objects.get(pk=resp.data['id']).meta_data == {'day': 'Wednesday'}


@pytest.mark.django_db
def test_item_create_with_variation(token_client, organizer, event, item, category, taxrule):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': True,
            'issue_giftcard': False,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'variations': [
                {
                    'value': {'de': 'Kommentar', 'en': 'Comment'},
                    'active': True,
                    'description': None,
                    'position': 0,
                    'default_price': None,
                    'price': '23.00',
                }
            ],
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        new_item = Item.objects.get(pk=resp.data['id'])
        assert new_item.variations.first().value.localize('de') == 'Kommentar'
        assert new_item.variations.first().value.localize('en') == 'Comment'


@pytest.mark.django_db
def test_item_create_giftcard_validation(token_client, organizer, event, item, category, category2, taxrule, taxrule0):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule0.pk,
            'admission': True,
            'issue_giftcard': True,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'addons': [],
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"non_field_errors":["Gift card products should not be admission products at the same time."]}'
    )
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': False,
            'issue_giftcard': True,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'addons': [],
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode() == '{"non_field_errors":["Gift card products should not be associated with non-zero '
        'tax rates since sales tax will be applied when the gift card is redeemed."]}'
    )


@pytest.mark.django_db
def test_item_create_with_addon(token_client, organizer, event, item, category, category2, taxrule):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': True,
            'issue_giftcard': False,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'addons': [
                {
                    'addon_category': category.pk,
                    'min_count': 0,
                    'max_count': 10,
                    'position': 0,
                    'multi_allowed': False,
                    'price_included': True,
                }
            ],
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        item = Item.objects.get(pk=resp.data['id'])
        assert item.addons.first().addon_category == category
        assert item.addons.first().max_count == 10
        assert 2 == Item.objects.all().count()

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': True,
            'issue_giftcard': False,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'addons': [
                {
                    'addon_category': category2.pk,
                    'min_count': 0,
                    'max_count': 10,
                    'position': 0,
                    'multi_allowed': False,
                    'price_included': True,
                }
            ],
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"addons":["The add-on\'s category must belong to the same event as the item."]}'
    with scopes_disabled():
        assert 2 == Item.objects.all().count()

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': True,
            'issue_giftcard': False,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'addons': [
                {
                    'addon_category': category.pk,
                    'min_count': 110,
                    'max_count': 10,
                    'position': 0,
                    'multi_allowed': False,
                    'price_included': True,
                }
            ],
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"addons":["The maximum count needs to be greater than the minimum count."]}'
    with scopes_disabled():
        assert 2 == Item.objects.all().count()

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': True,
            'issue_giftcard': True,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'addons': [
                {
                    'addon_category': category.pk,
                    'min_count': -1,
                    'max_count': 10,
                    'position': 0,
                    'multi_allowed': False,
                    'price_included': True,
                }
            ],
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() in [
        '{"addons":["The minimum count needs to be equal to or greater than zero."]}',
        '{"addons":[{"min_count":["Ensure this value is greater than or equal to 0."]}]}',
    ]
    with scopes_disabled():
        assert 2 == Item.objects.all().count()


@pytest.mark.django_db
def test_item_create_with_bundle(token_client, organizer, event, item, category, item2, taxrule):
    with scopes_disabled():
        i = event.items.create(name='Included thing', default_price=2)
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': True,
            'issue_giftcard': False,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'bundles': [
                {
                    'bundled_item': i.pk,
                    'bundled_variation': None,
                    'count': 2,
                    'designated_price': '3.00',
                }
            ],
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        item = Item.objects.get(pk=resp.data['id'])
        b = item.bundles.first()
    assert b.bundled_item == i
    assert b.bundled_variation is None
    assert b.count == 2
    assert b.designated_price == 3

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': True,
            'issue_giftcard': False,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'bundles': [
                {
                    'bundled_item': item2.pk,
                    'bundled_variation': None,
                    'count': 2,
                    'designated_price': '3.00',
                }
            ],
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"bundles":["The bundled item must belong to the same event as the item."]}'

    with scopes_disabled():
        v = item2.variations.create(value='foo')
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug),
        {
            'category': category.pk,
            'name': {'en': 'Ticket'},
            'active': True,
            'description': None,
            'default_price': '23.00',
            'free_price': False,
            'tax_rate': '19.00',
            'tax_rule': taxrule.pk,
            'admission': True,
            'position': 0,
            'picture': None,
            'available_from': None,
            'available_until': None,
            'require_voucher': False,
            'hide_without_voucher': False,
            'allow_cancel': True,
            'min_per_order': None,
            'max_per_order': None,
            'checkin_attention': False,
            'has_variations': True,
            'bundles': [
                {
                    'bundled_item': item.pk,
                    'bundled_variation': v.pk,
                    'count': 2,
                    'designated_price': '3.00',
                }
            ],
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"bundles":["The chosen variation does not belong to this item."]}'


@pytest.mark.django_db
def test_item_update(token_client, organizer, event, item, category, item2, category2, taxrule2):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {'min_per_order': 1, 'max_per_order': 2},
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        assert Item.objects.get(pk=item.pk).max_per_order == 2

    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {'min_per_order': 10, 'max_per_order': 2},
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode() == '{"non_field_errors":["The maximum number per order can not be lower than the '
        'minimum number per order."]}'
    )

    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {'available_from': '2017-12-30T12:00', 'available_until': '2017-12-29T12:00'},
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"non_field_errors":["The item\'s availability cannot end before it starts."]}'

    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {'category': category2.pk},
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"category":["The item\'s category must belong to the same event as the item."]}'

    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {'tax_rule': taxrule2.pk},
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"tax_rule":["The item\'s tax rule must belong to the same event as the item."]}'

    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {
            'addons': [
                {
                    'addon_category': category.pk,
                    'min_count': 0,
                    'max_count': 10,
                    'position': 0,
                    'multi_allowed': False,
                    'price_included': True,
                }
            ]
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use '
        'the dedicated nested endpoint."]}'
    )

    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {
            'bundles': [
                {
                    'bundled_item': item2.pk,
                    'bundled_variation': None,
                    'count': 2,
                    'designated_price': '3.00',
                }
            ]
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use '
        'the dedicated nested endpoint."]}'
    )

    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {'meta_data': {'day': 'Friday'}},
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        assert Item.objects.get(pk=item.pk).meta_data == {'day': 'Friday'}

    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {'meta_data': {'foo': 'bar'}},
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"meta_data":["Item meta data property \'foo\' does not exist."]}'


@pytest.mark.django_db
def test_item_update_with_variation(token_client, organizer, event, item):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {
            'variations': [
                {
                    'value': {'de': 'Kommentar', 'en': 'Comment'},
                    'active': True,
                    'description': None,
                    'position': 0,
                    'default_price': None,
                    'price': '23.00',
                }
            ]
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use '
        'the dedicated nested endpoint."]}'
    )


@pytest.mark.django_db
def test_item_update_with_addon(token_client, organizer, event, item, category):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk),
        {
            'addons': [
                {
                    'addon_category': category.pk,
                    'min_count': 0,
                    'max_count': 10,
                    'position': 0,
                    'multi_allowed': False,
                    'price_included': True,
                }
            ]
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"non_field_errors":["Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use '
        'the dedicated nested endpoint."]}'
    )


@pytest.mark.django_db
def test_items_delete(token_client, organizer, event, item):
    resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
    assert resp.status_code == 204
    with scopes_disabled():
        assert not event.items.filter(pk=item.id).exists()


@pytest.mark.django_db
def test_items_with_order_position_not_delete(token_client, organizer, event, item, order_position):
    resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
    assert resp.status_code == 403
    with scopes_disabled():
        assert event.items.filter(pk=item.id).exists()


@pytest.mark.django_db
def test_items_with_cart_position_delete(token_client, organizer, event, item, cart_position):
    resp = token_client.delete('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug, item.pk))
    assert resp.status_code == 204
    with scopes_disabled():
        assert not event.items.filter(pk=item.id).exists()


@pytest.fixture
def variations(item):
    v = []
    v.append(item.variations.create(value='ChildA1'))
    v.append(item.variations.create(value='ChildA2'))
    return v


@pytest.fixture
def variations2(item2):
    v = []
    v.append(item2.variations.create(value='ChildB1'))
    v.append(item2.variations.create(value='ChildB2'))
    return v


@pytest.fixture
def variation(item):
    return item.variations.create(value='ChildC1')


TEST_VARIATIONS_RES = {
    'value': {'en': 'ChildC1'},
    'active': True,
    'description': None,
    'position': 0,
    'default_price': None,
    'price': '23.00',
    'original_price': None,
}

TEST_VARIATIONS_UPDATE = {
    'value': {'en': 'ChildC2'},
    'active': True,
    'description': None,
    'position': 1,
    'default_price': '20.0',
    'original_price': None,
}


@pytest.mark.django_db
def test_variations_list(token_client, organizer, event, item, variation):
    res = dict(TEST_VARIATIONS_RES)
    res['id'] = variation.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/'.format(organizer.slug, event.slug, item.pk)
    )
    assert resp.status_code == 200
    assert res['value'] == resp.data['results'][0]['value']
    assert res['position'] == resp.data['results'][0]['position']
    assert res['price'] == resp.data['results'][0]['price']


@pytest.mark.django_db
def test_variations_detail(token_client, organizer, event, item, variation):
    res = dict(TEST_VARIATIONS_RES)
    res['id'] = variation.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(
            organizer.slug, event.slug, item.pk, variation.pk
        )
    )
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_variations_create(token_client, organizer, event, item, variation):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/'.format(organizer.slug, event.slug, item.pk),
        {
            'value': {'en': 'ChildC2'},
            'active': True,
            'description': None,
            'position': 1,
            'default_price': None,
            'original_price': '23.42',
            'price': 23.0,
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        var = ItemVariation.objects.get(pk=resp.data['id'])
    assert var.position == 1
    assert var.price == 23.0


@pytest.mark.django_db
def test_variations_create_not_allowed(token_client, organizer, event, item):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/'.format(organizer.slug, event.slug, item.pk),
        {
            'value': {'en': 'ChildC2'},
            'active': True,
            'description': None,
            'position': 1,
            'default_price': None,
            'price': 23.0,
        },
        format='json',
    )
    assert resp.status_code == 403
    assert (
        resp.content.decode() == '{"detail":"This variation cannot be created because the item does '
        'not have variations. Changing a product without variations to a product with '
        'variations is not allowed."}'
    )


@pytest.mark.django_db
def test_variations_update(token_client, organizer, event, item, item3, variation):
    res = dict(TEST_VARIATIONS_UPDATE)
    res['id'] = variation.pk
    res['price'] = '20.00'
    res['default_price'] = '20.00'
    res['original_price'] = '50.00'
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(
            organizer.slug, event.slug, item.pk, variation.pk
        ),
        {
            'value': {'en': 'ChildC2'},
            'position': 1,
            'default_price': '20.00',
            'original_price': '50.00',
        },
        format='json',
    )
    assert resp.status_code == 200
    assert res == resp.data

    # Variation exists but do not belong to item
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(
            organizer.slug, event.slug, item3.pk, variation.pk
        ),
        {'position': 1},
        format='json',
    )
    assert resp.status_code == 404


@pytest.mark.django_db
def test_variations_delete(token_client, organizer, event, item, variations, order):
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(
            organizer.slug, event.slug, item.pk, variations[0].pk
        )
    )
    assert resp.status_code == 204
    with scopes_disabled():
        assert not item.variations.filter(pk=variations[0].pk).exists()


@pytest.mark.django_db
def test_variations_with_order_position_not_delete(
    token_client, organizer, event, item, order, variations, order_position
):
    with scopes_disabled():
        assert item.variations.filter(pk=variations[0].id).exists()
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(
            organizer.slug, event.slug, item.pk, variations[0].pk
        )
    )
    assert resp.status_code == 403
    assert (
        resp.content.decode() == '{"detail":"This variation cannot be deleted because it has already been ordered '
        "by a user or currently is in a users's cart. Please set the variation as "
        "'inactive' instead.\"}"
    )
    with scopes_disabled():
        assert item.variations.filter(pk=variations[0].id).exists()


@pytest.mark.django_db
def test_variations_with_cart_position_not_delete(token_client, organizer, event, item, variations, cart_position):
    with scopes_disabled():
        assert item.variations.filter(pk=variations[0].id).exists()
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(
            organizer.slug, event.slug, item.pk, variations[0].pk
        )
    )
    assert resp.status_code == 403
    assert (
        resp.content.decode() == '{"detail":"This variation cannot be deleted because it has already been ordered '
        "by a user or currently is in a users's cart. Please set the variation as "
        "'inactive' instead.\"}"
    )
    with scopes_disabled():
        assert item.variations.filter(pk=variations[0].id).exists()


@pytest.mark.django_db
def test_only_variation_not_delete(token_client, organizer, event, item, variation):
    with scopes_disabled():
        assert item.variations.filter(pk=variation.id).exists()
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(
            organizer.slug, event.slug, item.pk, variation.pk
        )
    )
    assert resp.status_code == 403
    assert (
        resp.content.decode() == '{"detail":"This variation cannot be deleted because it is the only variation. '
        'Changing a product with variations to a product without variations is not '
        'allowed."}'
    )
    with scopes_disabled():
        assert item.variations.filter(pk=variation.id).exists()


@pytest.fixture
def bundle(item, item3, category):
    return item.bundles.create(bundled_item=item3, count=1, designated_price=2)


TEST_BUNDLE_RES = {
    'bundled_item': 0,
    'bundled_variation': None,
    'count': 1,
    'designated_price': '2.00',
}


@pytest.mark.django_db
def test_bundles_list(token_client, organizer, event, item, bundle, item3):
    res = dict(TEST_BUNDLE_RES)
    res['id'] = bundle.pk
    res['bundled_item'] = item3.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/items/{}/bundles/'.format(organizer.slug, event.slug, item.pk)
    )
    assert resp.status_code == 200
    assert res == resp.data['results'][0]


@pytest.mark.django_db
def test_bundles_detail(token_client, organizer, event, item, bundle, item3):
    res = dict(TEST_BUNDLE_RES)
    res['id'] = bundle.pk
    res['bundled_item'] = item3.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/items/{}/bundles/{}/'.format(organizer.slug, event.slug, item.pk, bundle.pk)
    )
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_bundles_create(token_client, organizer, event, item, item2, item3):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/{}/bundles/'.format(organizer.slug, event.slug, item.pk),
        {
            'bundled_item': item3.pk,
            'bundled_variation': None,
            'count': 1,
            'designated_price': '1.50',
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        b = ItemBundle.objects.get(pk=resp.data['id'])
    assert b.bundled_item == item3
    assert b.bundled_variation is None
    assert b.designated_price == 1.5
    assert b.count == 1

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/{}/bundles/'.format(organizer.slug, event.slug, item.pk),
        {
            'bundled_item': item2.pk,
            'bundled_variation': None,
            'count': 1,
            'designated_price': '1.50',
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode() == '{"non_field_errors":["The bundled item must belong to the same event as the item."]}'
    )

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/{}/bundles/'.format(organizer.slug, event.slug, item.pk),
        {
            'bundled_item': item.pk,
            'bundled_variation': None,
            'count': 1,
            'designated_price': '1.50',
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"non_field_errors":["The bundled item must not be the same item as the bundling one."]}'
    )

    with scopes_disabled():
        item3.bundles.create(bundled_item=item, count=1, designated_price=3)
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/{}/bundles/'.format(organizer.slug, event.slug, item.pk),
        {
            'bundled_item': item3.pk,
            'bundled_variation': None,
            'count': 1,
            'designated_price': '1.50',
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"non_field_errors":["The bundled item must not have bundles on its own."]}'


@pytest.mark.django_db
def test_bundles_update(token_client, organizer, event, item, bundle):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/bundles/{}/'.format(organizer.slug, event.slug, item.pk, bundle.pk),
        {
            'count': 3,
        },
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        a = ItemBundle.objects.get(pk=bundle.pk)
    assert a.count == 3


@pytest.mark.django_db
def test_bundles_delete(token_client, organizer, event, item, bundle):
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/items/{}/bundles/{}/'.format(organizer.slug, event.slug, item.pk, bundle.pk)
    )
    assert resp.status_code == 204
    with scopes_disabled():
        assert not item.bundles.filter(pk=bundle.id).exists()


@pytest.fixture
def addon(item, category):
    return item.addons.create(addon_category=category, min_count=0, max_count=10, position=1)


@pytest.fixture
def option(question):
    return question.options.create(answer='XL', identifier='LVETRWVU')


TEST_ADDONS_RES = {
    'min_count': 0,
    'max_count': 10,
    'position': 1,
    'multi_allowed': False,
    'price_included': False,
}


@pytest.mark.django_db
def test_addons_list(token_client, organizer, event, item, addon, category):
    res = dict(TEST_ADDONS_RES)
    res['id'] = addon.pk
    res['addon_category'] = category.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/items/{}/addons/'.format(organizer.slug, event.slug, item.pk)
    )
    assert resp.status_code == 200
    assert res['addon_category'] == resp.data['results'][0]['addon_category']
    assert res['min_count'] == resp.data['results'][0]['min_count']
    assert res['max_count'] == resp.data['results'][0]['max_count']
    assert res['position'] == resp.data['results'][0]['position']


@pytest.mark.django_db
def test_addons_detail(token_client, organizer, event, item, addon, category):
    res = dict(TEST_ADDONS_RES)
    res['id'] = addon.pk
    res['addon_category'] = category.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/items/{}/addons/{}/'.format(organizer.slug, event.slug, item.pk, addon.pk)
    )
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_addons_create(token_client, organizer, event, item, category, category2):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/{}/addons/'.format(organizer.slug, event.slug, item.pk),
        {
            'addon_category': category.pk,
            'min_count': 0,
            'max_count': 10,
            'position': 1,
            'multi_allowed': False,
            'price_included': False,
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        addon = ItemAddOn.objects.get(pk=resp.data['id'])
    assert addon.position == 1
    assert addon.addon_category == category

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/{}/addons/'.format(organizer.slug, event.slug, item.pk),
        {
            'addon_category': category.pk,
            'min_count': 10,
            'max_count': 20,
            'position': 2,
            'multi_allowed': False,
            'price_included': False,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"addon_category":["The item already has an add-on of this category."]}'

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/items/{}/addons/'.format(organizer.slug, event.slug, item.pk),
        {
            'addon_category': category2.pk,
            'min_count': 10,
            'max_count': 20,
            'position': 2,
            'multi_allowed': False,
            'price_included': False,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode() == '{"addon_category":["The add-on\'s category must belong to the same event as '
        'the item."]}'
    )


@pytest.mark.django_db
def test_addons_update(token_client, organizer, event, item, addon):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/addons/{}/'.format(organizer.slug, event.slug, item.pk, addon.pk),
        {'min_count': 100, 'max_count': 101},
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        a = ItemAddOn.objects.get(pk=addon.pk)
    assert a.min_count == 100
    assert a.max_count == 101

    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/items/{}/addons/{}/'.format(organizer.slug, event.slug, item.pk, a.pk),
        {'min_count': 10, 'max_count': 1},
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode() == '{"non_field_errors":["The maximum count needs to be greater than the minimum '
        'count."]}'
    )


@pytest.mark.django_db
def test_addons_delete(token_client, organizer, event, item, addon):
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/items/{}/addons/{}/'.format(organizer.slug, event.slug, item.pk, addon.pk)
    )
    assert resp.status_code == 204
    with scopes_disabled():
        assert not item.addons.filter(pk=addon.id).exists()


@pytest.fixture
def quota(event, item):
    q = event.quotas.create(name='Budget Quota', size=200)
    q.items.add(item)
    return q


TEST_QUOTA_RES = {
    'name': 'Budget Quota',
    'size': 200,
    'items': [],
    'variations': [],
    'subevent': None,
    'close_when_sold_out': False,
    'release_after_exit': False,
    'closed': False,
}


@pytest.mark.django_db
def test_quota_list(token_client, organizer, event, quota, item, subevent):
    res = dict(TEST_QUOTA_RES)
    res['id'] = quota.pk
    res['items'] = [item.pk]

    resp = token_client.get('/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [res] == resp.data['results']

    quota.subevent = subevent
    quota.save()
    res['subevent'] = subevent.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/quotas/?subevent={}'.format(organizer.slug, event.slug, subevent.pk)
    )
    assert [res] == resp.data['results']
    with scopes_disabled():
        se2 = event.subevents.create(name='Foobar', date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/quotas/?subevent={}'.format(organizer.slug, event.slug, se2.pk)
    )
    assert [] == resp.data['results']


@pytest.mark.django_db
def test_quota_detail(token_client, organizer, event, quota, item):
    res = dict(TEST_QUOTA_RES)

    res['id'] = quota.pk
    res['items'] = [item.pk]
    resp = token_client.get('/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk))
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_quota_create(token_client, organizer, event, event2, item):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [item.pk],
            'variations': [],
            'subevent': None,
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        quota = Quota.objects.get(pk=resp.data['id'])
    assert quota.name == 'Ticket Quota'
    assert quota.size == 200

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event2.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [item.pk],
            'variations': [],
            'subevent': None,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"non_field_errors":["One or more items do not belong to this event."]}'


@pytest.mark.django_db
def test_quota_create_with_variations(token_client, organizer, event, item, variations, variations2):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [item.pk],
            'variations': [variations[0].pk],
            'subevent': None,
        },
        format='json',
    )
    assert resp.status_code == 201

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [item.pk],
            'variations': [100],
            'subevent': None,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"variations":["Invalid pk \\"100\\" - object does not exist."]}'

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [item.pk],
            'variations': [variations[0].pk, variations2[0].pk],
            'subevent': None,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"non_field_errors":["All variations must belong to an item contained in the items list."]}'
    )

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [item.pk],
            'variations': [],
            'subevent': None,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"non_field_errors":["One or more items has variations but none of these are in the variations list."]}'
    )


@pytest.mark.django_db
def test_quota_create_with_subevent(token_client, organizer, event, event3, item, variations, subevent, subevent2):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [item.pk],
            'variations': [variations[0].pk],
            'subevent': subevent.pk,
        },
        format='json',
    )
    assert resp.status_code == 201

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [item.pk],
            'variations': [variations[0].pk],
            'subevent': None,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"non_field_errors":["Subevent cannot be null for event series."]}'

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [item.pk],
            'variations': [variations[0].pk],
            'subevent': subevent2.pk,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"non_field_errors":["The subevent does not belong to this event."]}'

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event3.slug),
        {
            'name': 'Ticket Quota',
            'size': 200,
            'items': [],
            'variations': [],
            'subevent': subevent2.pk,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"non_field_errors":["The subevent does not belong to this event."]}'


@pytest.mark.django_db
def test_quota_update(token_client, organizer, event, quota, item):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk),
        {
            'name': 'Ticket Quota Update',
            'size': 111,
        },
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        quota = Quota.objects.get(pk=resp.data['id'])
    assert quota.name == 'Ticket Quota Update'
    assert quota.size == 111
    assert quota.all_logentries().count() == 1


@pytest.mark.django_db
def test_quota_update_closed(token_client, organizer, event, quota, item):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk),
        {
            'closed': True,
        },
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        quota = Quota.objects.get(pk=resp.data['id'])
    assert quota.all_logentries().filter(action_type='pretix.event.quota.closed').count() == 1
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk),
        {
            'closed': False,
        },
        format='json',
    )
    assert resp.status_code == 200
    assert quota.all_logentries().filter(action_type='pretix.event.quota.opened').count() == 1


@pytest.mark.django_db
def test_quota_update_unchanged(token_client, organizer, event, quota, item):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk),
        {
            'size': 200,
        },
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        quota = Quota.objects.get(pk=resp.data['id'])
    assert quota.size == 200
    assert quota.all_logentries().count() == 0


@pytest.mark.django_db
def test_quota_delete(token_client, organizer, event, quota):
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/quotas/{}/'.format(organizer.slug, event.slug, quota.pk)
    )
    assert resp.status_code == 204
    with scopes_disabled():
        assert not event.quotas.filter(pk=quota.id).exists()


@pytest.mark.django_db
def test_quota_availability(token_client, organizer, event, quota, item):
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/quotas/{}/availability/'.format(organizer.slug, event.slug, quota.pk)
    )
    assert resp.status_code == 200
    assert {
        'blocking_vouchers': 0,
        'available_number': 200,
        'pending_orders': 0,
        'exited_orders': 0,
        'cart_positions': 0,
        'available': True,
        'total_size': 200,
        'paid_orders': 0,
        'waiting_list': 0,
    } == resp.data


@pytest.fixture
def question(event, item):
    q = event.questions.create(
        question='T-Shirt size',
        type='C',
        identifier='ABC',
        help_text='This is an example question',
    )
    q.items.add(item)
    q.options.create(answer='XL', identifier='LVETRWVU')
    return q


TEST_QUESTION_RES = {
    'question': {'en': 'T-Shirt size'},
    'type': 'C',
    'required': False,
    'items': [],
    'ask_during_checkin': False,
    'hidden': False,
    'print_on_invoice': False,
    'identifier': 'ABC',
    'position': 0,
    'dependency_question': None,
    'dependency_value': None,
    'dependency_values': [],
    'valid_number_min': None,
    'valid_number_max': None,
    'valid_date_min': None,
    'valid_date_max': None,
    'valid_datetime_min': None,
    'valid_datetime_max': None,
    'help_text': {'en': 'This is an example question'},
    'options': [{'id': 0, 'position': 0, 'identifier': 'LVETRWVU', 'answer': {'en': 'XL'}}],
}


@pytest.mark.django_db
def test_question_list(token_client, organizer, event, question, item):
    res = dict(TEST_QUESTION_RES)
    res['id'] = question.pk
    res['items'] = [item.pk]
    res['options'][0]['id'] = question.options.first().pk

    resp = token_client.get('/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event.slug))
    assert resp.status_code == 200
    assert [res] == resp.data['results']

    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/questions/?required=false'.format(organizer.slug, event.slug)
    )
    assert [res] == resp.data['results']
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/questions/?ask_during_checkin=false'.format(organizer.slug, event.slug)
    )
    assert [res] == resp.data['results']
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/questions/?identifier=ABC'.format(organizer.slug, event.slug)
    )
    assert [res] == resp.data['results']
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/questions/?required=true'.format(organizer.slug, event.slug)
    )
    assert [] == resp.data['results']
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/questions/?ask_during_checkin=true'.format(organizer.slug, event.slug)
    )
    assert [] == resp.data['results']
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/questions/?identifier=DEF'.format(organizer.slug, event.slug)
    )
    assert [] == resp.data['results']


@pytest.mark.django_db
def test_question_detail(token_client, organizer, event, question, item):
    res = dict(TEST_QUESTION_RES)

    res['id'] = question.pk
    res['items'] = [item.pk]
    res['options'][0]['id'] = question.options.first().pk

    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/questions/{}/'.format(organizer.slug, event.slug, question.pk)
    )
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_question_create(token_client, organizer, event, event2, item):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event.slug),
        {
            'question': "What's your name?",
            'type': 'S',
            'required': True,
            'items': [item.pk],
            'position': 0,
            'ask_during_checkin': False,
            'identifier': None,
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        question = Question.objects.get(pk=resp.data['id'])
        assert question.question == "What's your name?"
        assert question.type == 'S'
        assert question.identifier is not None
        assert len(question.items.all()) == 1

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event2.slug),
        {
            'question': "What's your name?",
            'type': 'S',
            'required': True,
            'items': [item.pk],
            'position': 0,
            'ask_during_checkin': False,
            'identifier': None,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"non_field_errors":["One or more items do not belong to this event."]}'

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event.slug),
        {
            'question': "What's your name?",
            'type': 'S',
            'required': True,
            'items': [item.pk],
            'position': 0,
            'ask_during_checkin': False,
            'identifier': question.identifier,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"identifier":["This identifier is already used for a different question."]}'

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event.slug),
        {
            'question': "What's your name?",
            'type': 'S',
            'required': True,
            'items': [item.pk],
            'position': 0,
            'ask_during_checkin': False,
            'dependency_question': question.pk,
            'dependency_value': '1',
            'identifier': None,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"dependency_question":["Question dependencies can only be set to boolean or choice questions."]}'
    )

    question.type = Question.TYPE_BOOLEAN
    question.save()
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event.slug),
        {
            'question': "What's your name?",
            'type': 'S',
            'required': True,
            'items': [item.pk],
            'position': 0,
            'ask_during_checkin': True,
            'dependency_question': question.pk,
            'dependency_value': '1',
            'identifier': None,
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"non_field_errors":["Dependencies are not supported during check-in."]}'

    question.type = Question.TYPE_BOOLEAN
    question.save()
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event.slug),
        {
            'question': "What's your name?",
            'type': 'S',
            'required': True,
            'items': [item.pk],
            'position': 0,
            'ask_during_checkin': False,
            'dependency_question': question.pk,
            'dependency_value': '1',
            'identifier': None,
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        q2 = Question.objects.get(pk=resp.data['id'])
    assert q2.dependency_question == question

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event.slug),
        {
            'question': "What's your name?",
            'type': 'S',
            'required': True,
            'items': [item.pk],
            'position': 0,
            'ask_during_checkin': False,
            'identifier': None,
            'dependency_question': None,
            'dependency_values': [],
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        question = Question.objects.get(pk=resp.data['id'])
        assert question.question == "What's your name?"
        assert question.type == 'S'
        assert question.identifier is not None
        assert len(question.items.all()) == 1
        assert question.dependency_question is None
        assert question.dependency_values == []


@pytest.mark.django_db
def test_question_update(token_client, organizer, event, question):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/questions/{}/'.format(organizer.slug, event.slug, question.pk),
        {
            'question': "What's your shoe size?",
            'type': 'N',
        },
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        question = Question.objects.get(pk=resp.data['id'])
    assert question.question == "What's your shoe size?"
    assert question.type == 'N'


@pytest.mark.django_db
def test_question_update_circular_dependency(token_client, organizer, event, question):
    with scopes_disabled():
        q2 = event.questions.create(
            question='T-Shirt size',
            type='B',
            identifier='FOO',
            dependency_question=question,
        )
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/questions/{}/'.format(organizer.slug, event.slug, question.pk),
        {'dependency_question': q2.pk},
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"non_field_errors":["Circular dependency between questions detected."]}'


@pytest.mark.django_db
def test_question_self_dependency(token_client, organizer, event, question):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/questions/{}/'.format(organizer.slug, event.slug, question.pk),
        {'dependency_question': question.pk},
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"dependency_question":["A question cannot depend on itself."]}'


@pytest.mark.django_db
def test_question_update_options(token_client, organizer, event, question, item):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/questions/{}/'.format(organizer.slug, event.slug, question.pk),
        {'options': []},
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"non_field_errors":["Updating options via PATCH/PUT is not supported. Please use the dedicated nested endpoint."]}'
    )


@pytest.mark.django_db
def test_question_delete(token_client, organizer, event, question):
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/questions/{}/'.format(organizer.slug, event.slug, question.pk)
    )
    assert resp.status_code == 204
    with scopes_disabled():
        assert not event.questions.filter(pk=question.id).exists()


@pytest.mark.django_db
def test_question_update_dependency_values(token_client, organizer, event, question):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/questions/{}/'.format(organizer.slug, event.slug, question.pk),
        {'dependency_values': ['a', 'b']},
        format='json',
    )
    assert resp.status_code == 200
    question.refresh_from_db()
    assert question.dependency_values == ['a', 'b']


@pytest.mark.django_db
def test_question_update_dependency_value_legacy(token_client, organizer, event, question):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/questions/{}/'.format(organizer.slug, event.slug, question.pk),
        {'dependency_value': 'a'},
        format='json',
    )
    assert resp.status_code == 200
    question.refresh_from_db()
    assert question.dependency_values == ['a']


@pytest.mark.django_db
def test_question_update_dependency_value_legacy_conflict(token_client, organizer, event, question):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/questions/{}/'.format(organizer.slug, event.slug, question.pk),
        {'dependency_values': ['a', 'b'], 'dependency_value': 'a'},
        format='json',
    )
    assert resp.status_code == 200
    question.refresh_from_db()
    assert question.dependency_values == ['a']


TEST_OPTIONS_RES = {'identifier': 'LVETRWVU', 'answer': {'en': 'XL'}, 'position': 0}


@pytest.mark.django_db
def test_options_list(token_client, organizer, event, question, option):
    res = dict(TEST_OPTIONS_RES)
    res['id'] = option.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/questions/{}/options/'.format(organizer.slug, event.slug, question.pk)
    )
    assert resp.status_code == 200
    assert res['identifier'] == resp.data['results'][0]['identifier']
    assert res['answer'] == resp.data['results'][0]['answer']


@pytest.mark.django_db
def test_options_detail(token_client, organizer, event, question, option):
    res = dict(TEST_OPTIONS_RES)
    res['id'] = option.pk
    resp = token_client.get(
        '/api/v1/organizers/{}/events/{}/questions/{}/options/{}/'.format(
            organizer.slug, event.slug, question.pk, option.pk
        )
    )
    assert resp.status_code == 200
    assert res == resp.data


@pytest.mark.django_db
def test_options_create(token_client, organizer, event, question):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/{}/options/'.format(organizer.slug, event.slug, question.pk),
        {'identifier': 'DFEMJWMJ', 'answer': 'A', 'position': 0},
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        option = QuestionOption.objects.get(pk=resp.data['id'])
    assert option.answer == 'A'

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/{}/options/'.format(organizer.slug, event.slug, question.pk),
        {'identifier': 'DFEMJWMJ', 'answer': 'A', 'position': 0},
        format='json',
    )
    assert resp.status_code == 400
    assert (
        resp.content.decode()
        == '{"identifier":["The identifier \\"DFEMJWMJ\\" is already used for a different option."]}'
    )


@pytest.mark.django_db
def test_options_update(token_client, organizer, event, question, option):
    resp = token_client.patch(
        '/api/v1/organizers/{}/events/{}/questions/{}/options/{}/'.format(
            organizer.slug, event.slug, question.pk, option.pk
        ),
        {
            'answer': 'B',
        },
        format='json',
    )
    assert resp.status_code == 200
    with scopes_disabled():
        a = QuestionOption.objects.get(pk=option.pk)
    assert a.answer == 'B'


@pytest.mark.django_db
def test_options_delete(token_client, organizer, event, question, option):
    resp = token_client.delete(
        '/api/v1/organizers/{}/events/{}/questions/{}/options/{}/'.format(
            organizer.slug, event.slug, question.pk, option.pk
        )
    )
    assert resp.status_code == 204
    with scopes_disabled():
        assert not question.options.filter(pk=option.id).exists()


@pytest.mark.django_db
def test_question_create_with_option(token_client, organizer, event, item):
    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event.slug),
        {
            'question': "What's your name?",
            'type': 'S',
            'required': True,
            'items': [item.pk],
            'position': 0,
            'ask_during_checkin': False,
            'identifier': None,
            'options': [
                {
                    'identifier': None,
                    'answer': {'en': 'A'},
                    'position': 0,
                },
                {
                    'identifier': None,
                    'answer': {'en': 'B'},
                    'position': 1,
                },
            ],
        },
        format='json',
    )
    assert resp.status_code == 201
    with scopes_disabled():
        question = Question.objects.get(pk=resp.data['id'])
        assert str(question.options.first().answer) == 'A'
        assert question.options.first().identifier is not None
        assert str(question.options.last().answer) == 'B'
        assert 2 == question.options.count()

    resp = token_client.post(
        '/api/v1/organizers/{}/events/{}/questions/'.format(organizer.slug, event.slug),
        {
            'question': "What's your name?",
            'type': 'S',
            'required': True,
            'items': [item.pk],
            'position': 0,
            'ask_during_checkin': False,
            'identifier': None,
            'options': [
                {
                    'identifier': 'ABC',
                    'answer': {'en': 'A'},
                    'position': 0,
                },
                {
                    'identifier': 'ABC',
                    'answer': {'en': 'B'},
                    'position': 1,
                },
            ],
        },
        format='json',
    )
    assert resp.status_code == 400
    assert resp.content.decode() == '{"options":["The identifier \\"ABC\\" is already used for a different option."]}'
